ref 的需求场景
reactive 只能用于对象类型,无法处理基本类型值(如数字、字符串)。当副作用之间需要共享一个响应式的基本类型值时,就需要 ref:
// 问题:salePrice 不是响应式的
let salePrice = product.price * 0.8
total = salePrice * product.quantity
// 解决:使用 ref 包装
const salePrice = ref(product.price * 0.8)
total = salePrice.value * product.quantity
javascript
JavaScript 对象访问器(Object Accessor)
ref 的核心原理基于 JavaScript 的对象访问器(getter/setter):
const user = {
firstName: 'Tom',
lastName: 'Cruise',
get fullName() {
return `${this.firstName} ${this.lastName}`
},
set fullName(value) {
const [first, last] = value.split(' ')
this.firstName = first
this.lastName = last
}
}
console.log(user.fullName) // "Tom Cruise"
user.fullName = "Tim Cook"
console.log(user.firstName) // "Tim"
javascript
ref 函数实现
利用对象访问器追踪 .value 属性的读写:
function ref(initialValue) {
const r = {
get value() {
track(r, 'value')
return initialValue
},
set value(newValue) {
initialValue = newValue
trigger(r, 'value')
}
}
return r
}
javascript
使用示例
const product = reactive({ price: 10, quantity: 3 })
const salePrice = ref(0)
effect(() => {
salePrice.value = product.price * 0.8
})
effect(() => {
total = salePrice.value * product.quantity
})
console.log(total) // 24 (10 * 0.8 * 3)
product.price = 20
console.log(total) // 48 (20 * 0.8 * 3)
product.quantity = 5
console.log(total) // 80 (20 * 0.8 * 5)
console.log(salePrice.value) // 16
javascript
ref 与 reactive 的区别
| 特性 | reactive | ref |
|---|---|---|
| 适用类型 | 对象 | 任意类型(含基本类型) |
| 访问方式 | 直接访问属性 obj.prop | 通过 .value 访问 |
| 实现原理 | Proxy 代理 | 对象访问器(getter/setter) |
| 动态属性 | 自动响应式 | 不适用 |
Vue 源码中的 ref
Vue 3 源码中 ref 的实际实现更为复杂,位于 packages/reactivity/src/ref.ts,核心逻辑相同但增加了更多边界处理:
- 类型标记(
__v_isRef) - 模板中自动解包(无需
.value) shallowRef(浅层响应式)toRef/toRefs(从 reactive 对象中提取 ref)customRef(自定义 ref 的 track/trigger 时机)
响应式 API 验证
可以通过构建 Vue 源码并编写 Node.js 测试脚本来验证自实现的逻辑与源码行为一致:
// test.js - 使用构建后的 reactivity.cjs.js
const { reactive, ref, computed, effect } = require('./reactivity.cjs')
const product = reactive({ price: 10, quantity: 3 })
const salePrice = ref(0)
const total = computed(() => salePrice.value * product.quantity)
effect(() => { salePrice.value = product.price * 0.8 })
console.log(total.value) // 24
product.price = 20
console.log(total.value) // 48
javascript
↑